前言
在HTTP中,最常用的GET
请求方是可缓存的(cacheable),通俗地说就是通过这种请求方式的资源会被浏览器缓存下来,如果所请求的资源没有任何变动,那么下一次请求就可以访问被缓存的资源,而不需要从服务器重新获取对应的资源。
情景
创建一个Web应用并把它部署到Tomcat中,假设我们使用Chrome访问某个静态页面a.html
,并用开发者工具查看Network
这一项,查看请求消息和响应消息。
第一次访问
请求消息1
2
3
4
5
6
7
8
9
10
11GET /day04-http/a.html HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4
Cookie: JSESSIONID=A26AF7FA34CE430A21E32D8666B05717; Idea-8752b670=e6037236-ceb5-4357-9600-1e08fca1a591;
_ga=GA1.1.327730390.1505579288; _gid=GA1.1.181028957.1507174466
响应消息1
2
3
4
5
6
7HTTP/1.1 200
Accept-Ranges: bytes
ETag: W/"220-1507175918413"
Last-Modified: Thu, 05 Oct 2017 03:58:38 GMT
Content-Type: text/html
Content-Length: 220
Date: Thu, 05 Oct 2017 03:59:51 GMT
请求消息没什么特别之处;响应消息中,状态码是200
,对应的状态描述是OK
,证明一切正常,而最值得注意的是响应头Last-Modified
:1
Last-Modified: Thu, 05 Oct 2017 03:58:38 GMT
这个时间是服务器告诉浏览器该请求资源的最后修改时间
这个时间正是a.html
的最后修改时间。默认是格林威治时间,这点我们不用去管,因为在HTTP的请求头和响应头中所有日期相关的都是格林威治时间。
第二次访问
现在,我们千万不要对a.html
进行任何变动,就像之前那样直接访问它,就能得到以下信息。
请求消息1
2
3
4
5
6
7
8
9
10
11
12
13GET /day04-http/a.html HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4
Cookie: JSESSIONID=A26AF7FA34CE430A21E32D8666B05717; Idea-8752b670=e6037236-ceb5-4357-9600-1e08fca1a591;
_ga=GA1.1.327730390.1505579288; _gid=GA1.1.181028957.1507174466
If-None-Match: W/"220-1507175918413"
If-Modified-Since: Thu, 05 Oct 2017 03:58:38 GMT
请求消息有了变化,其中最值得关注的一点就是请求头If-Modified-Since
:1
If-Modified-Since: Thu, 05 Oct 2017 03:58:38 GMT
这个时间正是第一次访问中,服务器给浏览器发过来响应头Last-Modified
的时间,这是浏览器告诉服务器该已被缓存的资源的最后修改时间。
响应消息1
2
3HTTP/1.1 304
ETag: W/"220-1507175918413"
Date: Thu, 05 Oct 2017 05:09:14 GMT
看到响应消息,比起第一次访问的响应消息,变化是巨大的。
这次的响应消息只有几行,最值得注意的是状态码304
,对应的状态描述是Not Modified
,这证明GET
请求的资源在服务器端没有被修改。
在这之后,如果我们不对静态资源a.html
进行修改,那么每次访问的请求消息和响应消息都会是以上所示的这种结果。
变动后再进行访问
现在你可以随便对a.html
进行改动,改动后再次访问,我们所看到的信息又会有所不同。
请求消息1
2
3
4
5
6
7
8
9
10
11
12
13GET /day04-http/a.html HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4
Cookie: JSESSIONID=A26AF7FA34CE430A21E32D8666B05717; Idea-8752b670=e6037236-ceb5-4357-9600-1e08fca1a591;
_ga=GA1.1.327730390.1505579288; _gid=GA1.1.181028957.1507174466
If-None-Match: W/"220-1507175918413"
If-Modified-Since: Thu, 05 Oct 2017 03:58:38 GMT
响应消息1
2
3
4
5
6
7HTTP/1.1 200
Accept-Ranges: bytes
ETag: W/"222-1507190102463"
Last-Modified: Thu, 05 Oct 2017 07:55:02 GMT
Content-Type: text/html
Content-Length: 222
Date: Thu, 05 Oct 2017 07:55:04 GMT
请求消息没什么太大的变动,我们应把集中力都放到响应消息中:
与第一次访问的没太大差异,但其中最重要的就是Last-Modified
,因为服务器端的a.html
内容变了,所以服务器向浏览器重新发送了最后修改时间。
如果在这之后再次访问a.html
,现在就知道肯定是If-Modified-Since
发生了变化,它的值会变成这次通过Last-Modified
传递过来的时间。
内部原理
通过前面的情景,现在我们都知道Last-Modified
和Last-Modified-Since
分别指的是什么:
Last-Modified
指的是服务器端资源的最后修改时间Last-Modified-Since
指的是已被浏览器缓存的资源的最后修改时间
而Last-Modified
和Last-Modified-Since
正如以下描述那样通力合作来控制缓存的:
浏览器通过
GET
方式向服务器第一次请求资源,则该资源会被浏览器缓存起来
同时服务器发送给浏览器的响应头Last-Modified
会告知该资源的最后修改时间,浏览器也会把它缓存起来在这之后,浏览器每一次通过
GET
方式向服务器请求该已被缓存的资源时
浏览器会把它对应的已被缓存起来的最后修改时间通过请求头Last-Modified-Since
发送给服务器服务器接到浏览器对该已被缓存的资源的
GET
方式请求后
首先是把服务器中对应实际资源的Last-Modified
以及浏览器发过来的Last-Modified-Since
进行对比
通俗来说就是:服务器对服务器中对应实际资源的最后修改时间与已被浏览器缓存的对应资源的最后修改时间两者进行对比如果两者的时间是一致,则证明在服务器上的对应实际资源没有改动,与被浏览器缓存的对应资源一致
那么就返回状态码304
,浏览器接收到这状态码就直接把之前缓存的内容重新拿出来显示如果两者的时间不一致,则明在服务器上的对应实际资源发生变动,与被浏览器缓存的对应资源不一致
那么就返回状态码200
,同时发送最新变动的资源以及通过响应头Last-Modified
发送已变动资源的最后修改时间给浏览器
最终,浏览器就会显示更新过的资源并且将它缓存起来,覆盖以前的缓存,对应的最后更新时间的缓存也同样会被重新覆盖
在HttpServlet中的应用
之前我们一直访问的都是静态资源,静态资源的缓存好控制,默认就已经实现了,我们直接就可以拿来用。如果是Servlet这种动态资源,又如何通过Last-Modified
与Last-Modified-Since
来控制缓存呢?
我们都知道Servlet是属于服务器这一端的,所以我们只管控制Last-Modified
即可。
查看源码
通过查看HttpServlet
的源码就可以发现:
其实HttpServlet
对于GET
请求的处理的原理也和前面所说的那样差不多,同时默认就已经有控制Last-Modified
的实现了。
1 | public abstract class HttpServlet extends GenericServlet { |
光从service
方法处理GET
请求的分支以及getLastModifed
方法这两段源码就可以得知很多信息了。
所以,我们最终的目的就是在自己的HttpServlet
里重写getLastModified
方法即可。
实际代码样例
先自定义一个HttpServlet
。
1 | import javax.servlet.ServletException; |
当你访问这个Servlet,并发现它所显示的时间是每隔10秒就变化1次的的证明缓存控制成功了!